import pyglet
from pyglet.gl import *

_root = None

def set_root(win):
    global _root
    _root = win

def root():
    return _root


class styledict(dict):
    def __init__(self, owner=None, initial=None):
        self.owner = owner
        dict.__init__(self)
        if initial:
            for k, v in initial.iteritems():
                self[k] = v

    def __setitem__(self, item, value):
        dict.__setitem__(self, item.lower(), value)
        if self.owner:
            self.owner.style_changed(item, value)

    def __getitem__(self, item):
        return dict.__getitem__(self, item.lower())

    def has_key(self, item):
        return dict.has_key(self, item.lower())


class Control(pyglet.event.EventDispatcher):
    """ controls have attributes like name, text etc """

    def __init__(self, *args, **kwargs):
        pyglet.event.EventDispatcher.__init__(self)
        self.style = styledict(self)
        parent = kwargs.pop('parent', None)
        self.name = kwargs.pop('name', None)
        self.classname = kwargs.pop('classname', None)
        self.layout = kwargs.pop('layout', None)
        self.create(*args, **kwargs)
        self.parent = parent 
        self.dirty = True
        self.root = None
        if parent:
            parent.append(self)

    def __str__(self):
        return "<%s name='%s' classname='%s'>" % (self.__class__.__name__,
                    self.name, self.classname)

    def create(self, **style):
        self.style.update(style)

    # copied directly from pyglet so we can add a better return value
    def dispatch_event(self, event_type, *args):
        '''Dispatch a single event to the attached handlers.

        The event is propogated to all handlers from from the top of the stack
        until one returns `EVENT_HANDLED`.  This method should be used only by
        `EventDispatcher` implementors; applications should call
        the ``dispatch_events`` method.

        :Parameters:
            `event_type` : str
                Name of the event.
            `args` : sequence
                Arguments to pass to the event handler.

        '''
        assert event_type in self.event_types

        # Search handler stack for matching event handlers
        for frame in list(self._event_stack):
            handler = frame.get(event_type, None)
            if handler:
                try:
                    if handler(*args):
                        return True
                except TypeError:
                    self._raise_dispatch_exception(event_type, args, handler)

        # Check instance for an event handler
        if hasattr(self, event_type):
            try:
                return getattr(self, event_type)(*args)
            except TypeError:
                self._raise_dispatch_exception(
                    event_type, args, getattr(self, event_type))

        return False

    def dispatch_mouse_event(self, event_type, x, y, *args):
        return self.dispatch_event(event_type, x, y, *args)

    def on_resize(self, width, height):
        if self.layout: 
            self.layout(self)

    def style_changed(self, item, value):
        pass

    def remove_computed_style(self):
        try:
            del self.computed_style
        except AttributeError:
            pass

Control.register_event_type("on_mouse_press")
Control.register_event_type("on_mouse_release")
Control.register_event_type("on_mouse_motion")
Control.register_event_type("on_mouse_drag")
Control.register_event_type("on_key_press")
Control.register_event_type("on_key_release")


class BaseControlGroup(Control):
    """ this class needs to be mixed with a class implementing an iterable
        eg:
        class ControlList(BaseControlGroup, list):
            pass
    """
    def dispatch_event(self, event_type, *args):
        for child in reversed(self):
            if child.dispatch_event(event_type, *args):
                return True
        try:
            return Control.dispatch_event(self, event_type, *args)
        except AssertionError:
            print event_type, args
            raise

    def dispatch_mouse_event(self, event_type, x, y, *args):
        for child in reversed(self):
            try:
                cs = child.style
            except AttributeError, e:
                # ... not sure why we see this occasionly
                print e, child.name, child.classname
                continue
            if (x > cs['x'] and 
                y > cs['y'] and
                x < cs['x'] + cs['width'] and
                y < cs['y'] + cs['height']):
                return child.dispatch_mouse_event(event_type, 
                                x-cs['x'], y-cs['y'], *args)
        try:
            return Control.dispatch_event(self, event_type, x, y, *args)
        except AssertionError:
            print event_type, args
            raise
        


class ControlList(BaseControlGroup, list):
    def __init__(self, *args, **kwargs):
        list.__init__(self, kwargs.pop('children', []))
        BaseControlGroup.__init__(self, *args, **kwargs)


class RootWindow(ControlList):
    def root_on_mouse_press(self, *args):
        self.dispatch_mouse_event('on_mouse_press', *args)

    def root_on_mouse_release(self, *args):
        self.dispatch_mouse_event('on_mouse_release', *args)

    def root_on_mouse_motion(self, *args):
        self.dispatch_mouse_event('on_mouse_motion', *args)

    def root_on_mouse_drag(self, *args):
        self.dispatch_mouse_event('on_mouse_drag', *args)

    def root_on_resize(self, width, height):
        self.style['width'] = width
        self.style['height'] = height
        #self.dispatch_event('on_resize', width, height)

    def root_on_key_press(self, s, m):
        if self.keyboard_focused:
            self.keyboard_focused.dispatch_event('on_key_press', s, m)
        else:
            self.dispatch_event('on_key_press', s, m)

    def root_on_key_release(self, s, m):
        if self.keyboard_focused:
            self.keyboard_focused.dispatch_event('on_key_release', s, m)
        else:
            self.dispatch_event('on_key_release', s, m)

    def create(self, window, renderer, **kwargs):
        set_root(self)

        ControlList.create(self, **kwargs)

        self.keyboard_focused = None

        # a little bit of magic i couldnt resist ...
        def render():
            renderer.render(self)
        self.render = render

        window.push_handlers(  
            on_mouse_press=self.root_on_mouse_press,
            on_mouse_release=self.root_on_mouse_release,
            on_mouse_motion=self.root_on_mouse_motion,
            on_mouse_drag=self.root_on_mouse_drag,
            on_resize=self.root_on_resize,
        )
    
        self.window = window


class Label(Control):
    def create(self, label=None, **kwargs):
        self.label = label
        Control.create(self, **kwargs)


class Button(Label):
    pass


class Frame(ControlList):
    def create(self, label=None, **kwargs):
        ControlList.create(self, **kwargs)
        self.label = label


class Window(Frame):
    class DragHandler(object):
        def __init__(self, win, offset):
            self.win = win
            self.offset = offset
        def on_mouse_drag(self, x, y, dx, dy, b, m):
            self.win.style['x'] = x - self.offset[0]
            self.win.style['y'] = y - self.offset[1]
            self.win.remove_computed_style()
        def on_mouse_release(self, x, y, b, m):
            root().window.pop_handlers()

    class ResizeHandler(object):
        def __init__(self, win):
            self.win = win
        def on_mouse_drag(self, x, y, dx, dy, b, m):
            self.win.style['width'] += dx
            self.win.style['y'] += dy
            self.win.style['height'] -= dy
            self.win.remove_computed_style()
            self.win.dirty = True
        def on_mouse_release(self, x, y, b, m):
            root().window.pop_handlers()

    def create(self, **kwargs):
        Frame.create(self, **kwargs)

    def on_mouse_press(self, x, y, button, modifiers):
        cs = self.computed_style
        if y > cs['height'] - cs['title_height']:
            root().window.push_handlers(self.DragHandler(self, (x, y)))
        elif (y < cs['resize_handle_size'] and 
            x > cs['width'] - cs['resize_handle_size']):
            root().window.push_handlers(self.ResizeHandler(self))


class Style(styledict):
    """ style class, styles controls using something like css """

    def __init__(self, *args, **kwargs):
        styledict.__init__(self, *args, **kwargs)
        self.cache = {}

    def getForControl(self, control):
        controlname = control.__class__.__name__
        classname = control.classname
        name = control.name
        cache_key = (name, classname, controlname)

        try:
            ret = self.cache[cache_key]
        except KeyError:
            pass

        self.cache[cache_key] = cache = {}

        # order: 
        # (control) base classes
        # (control.classname) base classes + classname
        # (.classname) classname
        # (control) current class
        # (control.classname) current class + classname
        # (#name) name

        self._apply_bases(control.__class__.__bases__, classname, cache)

        if self.has_key(controlname):
            cache.update(self['%s' % controlname])

        if classname and self.has_key('.%s' % classname):
            cache.update(self['.%s' % classname])

        if classname and self.has_key('%s.%s' % (controlname, classname)):
            cache.update(self['%s.%s' % (controlname, classname)])

        if name and self.has_key('#%s' % name):
            cache.update(self['#%s' % name])

        return cache

    def _apply_bases(self, bases, classname, ret):
        # we have to mess around to maintain order
        for base in bases:
            if base.__bases__:
                self._apply_bases(base.__bases__, classname, ret)
        for base in bases:
            base_name = base.__name__
            if self.has_key(base_name):
                ret.update(self[base_name])
        for base in bases:
            base_name = base.__name__
            if classname and self.has_key('%s.%s' % (base_name, classname)):
                ret.update(self['%s.%s' % (base_name, classname)])


class RenderContext(dict):
    """ this can be used for any control specific data passed 
    to the renderer """
    pass


class Renderer(styledict):
    """ renderers take controls and a style and produce some graphics """

    def __init__(self, style, initial):
        styledict.__init__(self, initial=initial)
        self.style = style 
        self.cache = {}

    def render(self, controls):
        style = self.style
        [self.render_control(c, style) for c in controls]

    def render_control(self, control, style, parent=None):
        controlname = control.__class__.__name__
        try:        
            control_style = control.computed_style
        except AttributeError:
            control_style = control.computed_style = \
                    style.getForControl(control).copy()
            control_style.update(control.style)

        try:
            render_func = self.cache[controlname]
        except KeyError:
            try:
                render_func = self[controlname]
            except KeyError:
                render_func = self.cache[controlname] = \
                        self._find_renderer(control.__class__.__bases__)

        if render_func:
            try:
                context = control.render_context
            except AttributeError:
                context = control.render_context = RenderContext()               
            glPushMatrix()
            glTranslatef(control_style['x'], control_style['y'], 0.0)
            render_func(control, control_style, context)

            if hasattr(control, '__iter__'):
                [self.render_control(c, style, self) for c in control]
                    
            glPopMatrix()
        else:
            print "not rendering", control, controlname

    def _find_renderer(self, bases):
        for base in bases:
            base_name = base.__name__
            if self.has_key(base_name):
                return self[base_name]            
            if base.__bases__:
                r = self._find_renderer(base.__bases__)
                if r: 
                    return r



               
